#include "stdafx.h"
#include "FourierTransformFactoryclFFT.h"
#include "includeclFFT.h"
#include "fftutils.h"
#include "delayloadUtils.h"

using namespace FTLib;

static float NormalizationToclFFTScaleFactor(uint32_t width, uint32_t height, double normalization)
{
	float f;
	if (IsNormalizationFactorDivBySize(normalization))
	{
		f = 1.0f / ((float)(width*height));
	}
	else if (IsNormalizationFactorDivBySqrtSize(normalization))
	{
		f = 1.0f / sqrtf((float)(width*height));
	}
	else
	{
		f = 1.0f / ((float)normalization);
	}

	return f;
}

// TODO: no, the packed layout of IPP is different from FFTW, we need an extra interface for this...
class CRealToComplex_PackedFftwComplexclFFTFft : public IRealToComplex_PackedFftwComplex
{
protected:
	uint32_t width, height;
	OPENCLFTPLANHANDLE plan;
public:
	CRealToComplex_PackedFftwComplexclFFTFft(uint32_t width, uint32_t height, double normalization) : width(width), height(height)
	{
		float f = NormalizationToclFFTScaleFactor(this->width, this->height, normalization);
		this->plan = clFFT_CreatePlan_2dRealToComplexPackedFftw(width, height, f);
	}

	~CRealToComplex_PackedFftwComplexclFFTFft()
	{
		clFFT_DeletePlan(this->plan);
	}

	virtual void RealToComplex_PackedFftwComplex(const FourierTransfRealInputData2d& input, const FourierTransfComplexOutput2d& output, const FourierTransformAdditional* pAdditional)
	{
		clFFT_2d_RealToComplexPackedFftw(this->plan, input, output);
	}
};

class CRealToComplex_ComplexclFFTFft : public IRealToComplex_Complex, protected CRealToComplex_PackedFftwComplexclFFTFft
{
public:
	CRealToComplex_ComplexclFFTFft(uint32_t width, uint32_t height, double normalization) : CRealToComplex_PackedFftwComplexclFFTFft(width, height, normalization)
	{}

	virtual void RealToComplex_Complex(const FourierTransfRealInputData2d& input, const FourierTransfComplexOutput2d& output, const FourierTransformAdditional* pAdditional)
	{
		FourierTransfComplexOutput2d outputPacked;
		outputPacked.width = this->width / 2 + 1;
		outputPacked.height = this->height;
		size_t sizeDestBuffer = outputPacked.width*outputPacked.height *sizeof(float) * 2;
		outputPacked.ptrData = (float*)_aligned_malloc(sizeDestBuffer, 32);
		outputPacked.pitch = outputPacked.width*sizeof(float) * 2;

		this->RealToComplex_PackedFftwComplex(input, outputPacked,pAdditional);
		ConvertFftwToComplex(input.width, input.height, outputPacked.ptrData, output.ptrData, output.pitch);
		_aligned_free(outputPacked.ptrData);
	}
};

class CComplexToComplexFwd_ComplexclFFTFftbase
{
protected:
	uint32_t width, height;
	OPENCLFTPLANHANDLE plan;
public:
	CComplexToComplexFwd_ComplexclFFTFftbase(uint32_t width, uint32_t height, double normalization) : width(width), height(height)
	{
		float f = NormalizationToclFFTScaleFactor(this->width, this->height, normalization);
		this->plan = clFFT_CreatePlan_2dComplexToComplex(width, height, f);
	}

	~CComplexToComplexFwd_ComplexclFFTFftbase()
	{
		clFFT_DeletePlan(this->plan);
	}
};

class CComplexToComplexFwd_ComplexclFFTFft : public CComplexToComplexFwd_ComplexclFFTFftbase, public IComplexToComplexFwd_Complex
{
public:
	CComplexToComplexFwd_ComplexclFFTFft(uint32_t width, uint32_t height, double normalization) : CComplexToComplexFwd_ComplexclFFTFftbase(width, height, normalization){}

	virtual void ComplexToComplex_Complex(const FourierTransfComplexInput2d& input, const FourierTransfComplexOutput2d& output, const FourierTransformAdditional* pAdditional)
	{
		clFFT_2d_ComplexToComplex(this->plan, true, input, output);
	}
};

class CComplexToComplexInv_ComplexclFFTFft : public CComplexToComplexFwd_ComplexclFFTFftbase, public IComplexToComplexInv_Complex
{
public:
	CComplexToComplexInv_ComplexclFFTFft(uint32_t width, uint32_t height, double normalization) : CComplexToComplexFwd_ComplexclFFTFftbase(width, height, normalization){}

	virtual void ComplexToComplex_Complex(const FourierTransfComplexInput2d& input, const FourierTransfComplexOutput2d& output, const FourierTransformAdditional* pAdditional)
	{
		clFFT_2d_ComplexToComplex(this->plan, false, input, output);
	}
};

class CComplexHermetianToReal_ComplexclFFTFft : public IComplexHermitianToReal
{
protected:
	uint32_t width, height;
	OPENCLFTPLANHANDLE plan;
public:
	CComplexHermetianToReal_ComplexclFFTFft(uint32_t width, uint32_t height, double normalization) : width(width), height(height)
	{
		float f = NormalizationToclFFTScaleFactor(this->width, this->height, normalization);
		this->plan = clFFT_CreatePlan_2dComplexHermetianToReal(width, height, f);
	}

	~CComplexHermetianToReal_ComplexclFFTFft()
	{
		clFFT_DeletePlan(this->plan);
	}

	virtual void ComplexHermitianToReal(const FourierTransfComplexInput2d& input, const FourierTransfRealOutputData2d& output, const FourierTransformAdditional* pAdditional)
	{
		clFFT_2d_ComplexHermetianToReal(this->plan, input, output);
	}
};

//------------------------------------------------------------------------------

/*static*/ bool CFourierTransformFactoryClFFTFft::CanLoadLibrary()
{
	return TryToLoadAllImports("FTLibOpenCL.dll");
}

/*static*/ bool CFourierTransformFactoryClFFTFft::CanOperate()
{
	return clFFT_CheckAvailability();
}

/*static*/void	CFourierTransformFactoryClFFTFft::Shutdown()
{
	clFFT_TearDown();
}

/*static*/ void CFourierTransformFactoryClFFTFft::GetVersionString(std::wstring& str)
{
	if (!GetIsAvailable())
	{
		str = L"ERROR";
		return;
	}

	size_t length;
	clFft_GetVersionString(nullptr, &length);
	length++;
	char* text = (char*)_malloca(length*sizeof(char));
	clFft_GetVersionString(text, &length);

	std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
	str = converter.from_bytes(text);
}

CFourierTransformFactoryClFFTFft::CFourierTransformFactoryClFFTFft(const std::wstring& initializationString)
{
}

/*virtual*/std::shared_ptr<IRealToComplex_PackedFftwComplex> CFourierTransformFactoryClFFTFft::CreateRealToComplexPackedFftw(const Options& opts)
{
	// not implemented
	return std::make_shared<CRealToComplex_PackedFftwComplexclFFTFft>(opts.width, opts.height, opts.normalizationFactor);
}

/*virtual*/std::shared_ptr<IRealToComplex_PackedIppComplex> CFourierTransformFactoryClFFTFft::CreateRealToComplexPackedIpp(const Options& opts)
{
	return std::shared_ptr<IRealToComplex_PackedIppComplex>();
}

/*virtual*/std::shared_ptr<IRealToComplex_Complex> CFourierTransformFactoryClFFTFft::CreateRealToComplex(const Options& opts)
{
	return std::make_shared<CRealToComplex_ComplexclFFTFft>(opts.width, opts.height, opts.normalizationFactor);
	//return std::shared_ptr<IRealToComplex_Complex>();
}

/*virtual*/std::shared_ptr<IRealToComplex_Complex> CFourierTransformFactoryClFFTFft::CreateRealToComplex_Shifted(const Options& opts)
{
	/*if (opts.width % 2 != 0 || opts.height % 2 != 0)
	{
	throw std::runtime_error("Only implemented for even width and height.");
	}*/

	//cufftHandle handle = cuFft_CreatePlan_2dRealToComplex_ZeroFreqInMiddle(opts.width, opts.height);
	return std::shared_ptr<IRealToComplex_Complex>();
}

/*virtual*/std::shared_ptr<FTLib::IComplexToComplexFwd_Complex> CFourierTransformFactoryClFFTFft::CreateComplexToComplexFwd(const Options& opts)
{
	return std::make_shared<CComplexToComplexFwd_ComplexclFFTFft>(opts.width, opts.height, opts.normalizationFactor);
}

/*virtual*/std::shared_ptr<IComplexToComplexInv_Complex> CFourierTransformFactoryClFFTFft::CreateComplexToComplexInv(const Options& opts)
{
	return std::make_shared<CComplexToComplexInv_ComplexclFFTFft>(opts.width, opts.height, opts.normalizationFactor);
}

/*virtual*/std::shared_ptr<FTLib::IComplexHermitianToReal> CFourierTransformFactoryClFFTFft::CreateComplexHermitianToReal(const Options& opts)
{
	return std::make_shared<CComplexHermetianToReal_ComplexclFFTFft>(opts.width, opts.height, opts.normalizationFactor);
}